﻿using System;
using System.Linq;
using System.Timers;
using SingleSignOn.Entities;
using System.ServiceModel;

namespace WebServices
{
    // NOTE: If you change the class name "AuthenticationService" here, you must also update the reference to "AuthenticationService" in Web.config.
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
                     MaxItemsInObjectGraph = Int32.MaxValue)]
    public class AuthenticationService : IAuthenticationService
    {
        private SecurityTokenCollection _securityTokenCollection = new SecurityTokenCollection();
        private OnlineUserCollection _onlineUserCollection = new OnlineUserCollection();
        private object _synchronizeObjectCall = new object();

        private TimeSpan _tokenTimeOut = new TimeSpan(0, 0, 120);
        private TimeSpan _loginTimeOut = new TimeSpan(0, 3, 0);
        private Timer _serviceTimer = new Timer(100);

        public AuthenticationService()
        {
            _serviceTimer.Elapsed += new ElapsedEventHandler(_serviceTimer_Elapsed);
            _serviceTimer.Start();
        }

        /// <summary>
        /// Validates the user.
        /// </summary>
        /// <param name="userName">Name of the user.</param>
        /// <param name="password">The password.</param>
        /// <returns></returns>
        public LoginResponse ValidateUser(LoginRequest loginRequest)
        {
            LoginResponse loginResponse = new LoginResponse();
            User user = new User();
            user.LoginId = loginRequest.User.LoginId;
            try
            {
                lock (_synchronizeObjectCall)
                {
                    // User is not yet logged in.
                    if (!_onlineUserCollection.Contains(loginRequest.User.LoginId))
                    {
                        _onlineUserCollection.Add(new OnlineUser()
                        {
                            LoginId = loginRequest.User.LoginId,
                            LoginTime = DateTime.Now.ToUniversalTime()
                        }
                        );
                    }
                    else // Request is coming for second time
                    {
                        _onlineUserCollection[loginRequest.User.LoginId].LoginTime = DateTime.Now.ToUniversalTime();
                    }
                }
                user.FirstName = "FirstName_" + loginRequest.User.LoginId;
                user.LastName = "LastName_" + loginRequest.User.LoginId;
            }
            catch (Exception ex)
            {
                loginResponse.Fault = new SingleSignOnFault();
                loginResponse.Fault.Message = ex.Message;
            }
            loginResponse.User = user;
            return loginResponse;
        }

        /// <summary>
        /// Gets the token.
        /// </summary>
        /// <param name="userName">Name of the user.</param>
        /// <returns></returns>
        public TokenResponse GetToken(TokenRequest tokenRequest)
        {
            TokenResponse response = new TokenResponse();
            response.Token = new Token();
            lock (_synchronizeObjectCall)
            {
                // Check if the user has already loggedin
                if (_onlineUserCollection.Contains(tokenRequest.User.LoginId))
                {
                    response.Token.Id = Guid.NewGuid().ToString();
                    SecurityToken securityToken = new SecurityToken()
                    {
                        User = tokenRequest.User,
                        Id = response.Token.Id,
                        CreateTime = DateTime.Now.ToUniversalTime()
                    };
                    _securityTokenCollection.Add(securityToken);
                }
            }
            return response;
        }

        /// <summary>
        /// Validates the token.
        /// </summary>
        /// <param name="token">The token.</param>
        /// <returns></returns>
        public TokenResponse ValidateToken(TokenRequest tokenRequest)
        {
            TokenResponse tokenResponse = new TokenResponse();
            tokenResponse.User = new User();

            lock (_synchronizeObjectCall)
            {
                if (_securityTokenCollection.Contains(tokenRequest.Token.Id))
                {
                    SecurityToken securityToken = _securityTokenCollection[tokenRequest.Token.Id];
                    TimeSpan differenceTime = DateTime.Now.ToUniversalTime() - securityToken.CreateTime;
                    if (differenceTime <= _tokenTimeOut)
                    {
                        tokenResponse.User = securityToken.User;
                    }
                    _securityTokenCollection.Remove(securityToken);
                }
            }
            return tokenResponse;
        }

        /// <summary>
        /// Sings the out.
        /// </summary>
        /// <param name="userName">Name of the user.</param>
        public void SingOut(string userName)
        {
            lock (_synchronizeObjectCall)
            {
                if (_onlineUserCollection.Contains(userName))
                {
                    DeleteUser(userName);
                }
            }
        }

        /// <summary>
        /// Deletes the user.
        /// </summary>
        /// <param name="loginId">The login id.</param>
        private void DeleteUser(string loginId)
        {
            // Remove Security Tokens first
            DeleteSecurityTokensForUser(loginId);

            // Remove the User now
            _onlineUserCollection.Remove(_onlineUserCollection[loginId]);
        }

        /// <summary>
        /// Deletes the security tokens for user.
        /// </summary>
        /// <param name="userName">Name of the user.</param>
        private void DeleteSecurityTokensForUser(string userName)
        {
            // Delete Security Tokens
            var tokenDeleteQuery = from tokens in _securityTokenCollection
                                    where tokens.User.LoginId == userName
                                    select tokens;
            foreach (SecurityToken token in tokenDeleteQuery.ToList())
            {
                _securityTokenCollection.Remove(token);
            }
        }

        /// <summary>
        /// Gets the online users.
        /// </summary>
        /// <returns></returns>
        public OnlineUserCollection GetOnlineUsers()
        {
            return this._onlineUserCollection;
        }

        /// <summary>
        /// Gets the security token collection.
        /// </summary>
        /// <returns></returns>
        public SecurityTokenCollection GetSecurityTokenCollection()
        {
            return _securityTokenCollection;
        }

        /// <summary>
        /// Handles the Elapsed event of the _serviceTimer control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Timers.ElapsedEventArgs"/> instance containing the event data.</param>
        private void _serviceTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            DateTime currentDateTime = DateTime.Now.ToUniversalTime();

            var logintimeOutQuery = from users in _onlineUserCollection
                                   where (currentDateTime - users.LoginTime) > _loginTimeOut
                                   select users.LoginId;
            foreach (var loginId in logintimeOutQuery.ToList())
            {
                DeleteUser(loginId);
            }
            
            // Delete Security Tokens
            var tokenTimeOutQuery = from tokens in _securityTokenCollection
                                    where (currentDateTime - tokens.CreateTime) > _tokenTimeOut
                                    select tokens;
            foreach (SecurityToken token in tokenTimeOutQuery.ToList())
            {
                _securityTokenCollection.Remove(token);
            }
        }

    }
}
